package com.uoko.common.security;

import java.nio.charset.Charset;
import java.security.MessageDigest;

/**
 * <pre>
 * <b>MD5加、解密辅助工具.</b>
 * <b>Description:</b> 主要提供对字符串进行加密; 对加密后字符串进行解密.
 *    MD5的算法在RFC1321中定义, 给出了Test suite用来检验你的实现是否正确:
 *    MD5 ("") = d41d8cd98f00b204e9800998ecf8427e 
 *    MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
 *    MD5 ("123456") = e10adc3949ba59abbe56e057f20f883e 
 *    MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0 
 *    MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
 *    
 * <b>Author:</b> liuuhong@yeah.net
 * <b>Date:</b> 2014-01-01 上午10:00:01
 * <b>Copyright:</b> Copyright ©2010-2016 liuuhong Technology Co., Ltd. All rights reserved.
 * <b>Changelog:</b>
 *   Ver   Date                  Author              Detail
 *   ----------------------------------------------------------------------
 *   1.0   2014-01-01 10:00:01   liuuhong@yeah.net
 *         new file.
 * </pre>
 */
public abstract class MD5Util {

	/** 常量, KEY:'MD5' */
	public static final String KEY = "MD5";

	/** 常量, UTF-8 编码. */
	public static final String ENCODING = "UTF-8";

	/** 常量, 字符集编码, 默认:UTF-8. */
	public static final Charset CHARSET = Charset.forName(ENCODING);

	/** 常量, HEX 16进制对照字典. */
	public static final char HEX_DIGITS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

	/** 常量, 空的二进制数组, new byte[] {} */
	public static final byte[] EMPTY_BYTE = new byte[] {};

	/**
	 * 构造方法.
	 */
	protected MD5Util() {
		super();
	}

	/**
	 * 生成随机令牌
	 * 
	 * @return String
	 */
	public static String token() {
		long times = System.nanoTime();
		return encrypt(String.valueOf(times));
	}

	/**
	 * 对字符串进行MD5加密;<br/>
	 * 如果待加密的字符串为null, 则直接返回null.
	 * 
	 * @param str 待加密的字符串.
	 * @return String 加密后字符串.
	 */
	public static String encrypt(String str) {
		return encrypt(str, 32);
	}

	/**
	 * 对字符串进行MD5加密;<br/>
	 * 如果待加密的字符串为null, 则直接返回null.
	 * 
	 * @param str 待加密的字符串.
	 * @param len 加密长度.
	 * @return String 加密后字符串.
	 */
	public static String encrypt(String str, int len) {
		return encrypt(str, CHARSET, len);
	}

	/**
	 * 对字符串进行MD5加密;<br/>
	 * 如果待加密的字符串为null, 则直接返回null.
	 * 
	 * @param str 待加密的字符串.
	 * @param charset 文件转码字符集编码.
	 * @return String 加密后字符串.
	 */
	public static String encrypt(String str, Charset charset) {
		return encrypt(str, charset, 32);
	}

	/**
	 * 对字符串进行MD5加密;<br/>
	 * 如果待加密的字符串为null, 则直接返回null.
	 * 
	 * @param str 待加密的字符串.
	 * @param charset 文件转码字符集编码.
	 * @param len 加密长度.
	 * @return String 加密后字符串.
	 */
	public static String encrypt(String str, Charset charset, int len) {
		byte[] bytes = getBytes(str, charset);
		return encrypt(bytes, len);
	}

	/**
	 * 对二进制进行MD5加密;<br/>
	 * 如果待加密的二进制为null, 则直接返回null.
	 * 
	 * @param bytes 待加密的二进制.
	 * @param len 加密长度.
	 * @return String 加密后二进制.
	 */
	public static String encrypt(byte[] bytes, int len) {
		if (null == bytes) {
			return null;
		}

		// 每个字节用 16 进制表示的话, 使用两个字符, 所以表示成 16 进制需要 32 个字符
		char[] chars = new char[16 * 2];
		// 表示转换结果中对应的字符位置
		int k = 0;
		String str = null;
		try {
			// 用来将字节转换成 16 进制表示的字符
			MessageDigest md = MessageDigest.getInstance(KEY);
			md.update(bytes);
			// MD5 的计算结果是一个 128 位的长整数, 用字节表示就是 16 个字节
			byte _bytes[] = md.digest();
			// 从第一个字节开始, 对 MD5 的每一个字节转换成 16 进制字符的转换
			for (int i = 0; i < 16; i++) {
				// 取第 i 个字节
				byte bt = _bytes[i];
				// 取字节中高 4 位的数字转换, >>> 为逻辑右移, 将符号位一起右移
				chars[k++] = HEX_DIGITS[bt >>> 4 & 0xf];
				// 取字节中低 4 位的数字转换
				chars[k++] = HEX_DIGITS[bt & 0xf];
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}

		// 换后的结果转换为字符串
		str = new String(chars);

		// 转为16位签名数字
		if (len == 16) {
			return str.substring(8, 24);
		}

		return str;
	}

	public static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length);
		for (int i = 0; i < bytes.length; i++) {
			sb.append(HEX_DIGITS[(bytes[i] & 0xf0) >>> 4]);
			sb.append(HEX_DIGITS[bytes[i] & 0x0f]);
		}
		return sb.toString();
	}

	/**
	 * 将字符串转换二进制数组, 默认字符集编码: ENCODING = "UTF-8".<br/>
	 * 如果被加密字符串为null 或 长度为0时, 则直接返回 null.
	 * 
	 * @param str 待转换的字符串.
	 * @return byte[] 转换后的二进制数组.
	 */
	public static byte[] getBytes(String str) {
		return getBytes(str, CHARSET);
	}

	/**
	 * 将字符串转为二进制数组, 需要指定具体字符集编码.<br/>
	 * 如果被加密字符串为null时, 则直接返回 null;<br/>
	 * 如果转换指定的字符集编码为null 或 长度为0时, 则直接返回 null.
	 * 
	 * @param str 待转换的字符串.
	 * @param encoding 指定的字符编码.
	 * @return byte[] 转换后的二进制数组.
	 */
	public static byte[] getBytes(String str, String encoding) {
		if (null == str || null == encoding || encoding.length() == 0) {
			return null;
		}

		Charset charset = Charset.forName(encoding);
		return getBytes(str, charset);
	}

	/**
	 * 将字符串转为二进制数组, 需要指定具体字符集编码.<br/>
	 * 如果被加密字符串为null时, 则直接返回 null;<br/>
	 * 如果转换指定的字符集编码为null 或 长度为0时, 则直接返回 null.
	 * 
	 * @param str 待转换的字符串.
	 * @param charset 指定的字符编码.
	 * @return byte[] 转换后的二进制数组.
	 */
	public static byte[] getBytes(String str, Charset charset) {
		if (null == str || null == charset) {
			return null;
		}
		if (0 == str.length()) {
			return EMPTY_BYTE;
		}

		byte[] bytes = null;
		try {
			bytes = str.getBytes(charset);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return bytes;
	}
}
